package authorizer

import (
	"encoding/json"
	"fmt"
	baseLog "gitee.com/zaiqiang231/go-base-app/base_app/log"
	"gitee.com/zaiqiang231/lovesport-authz-service/app/analytics"
	"gitee.com/zaiqiang231/lovesport-authz-service/app/constant"
	"gitee.com/zaiqiang231/lovesport-authz-service/app/store"
	"github.com/ory/ladon"
	"strings"
	"time"
)

type AuthorizationInterface interface {
	List(username string) ([]*ladon.DefaultPolicy, error)

	// The following two functions tracks denied and granted authorizations.
	LogRejectedAccessRequest(request *ladon.Request, pool ladon.Policies, deciders ladon.Policies)
	LogGrantedAccessRequest(request *ladon.Request, pool ladon.Policies, deciders ladon.Policies)
}

type AuthorizationClient struct {
}

func (auth *AuthorizationClient) List(username string) ([]*ladon.DefaultPolicy, error) {
	return store.GetCache().GetPolicy(username)
}

// LogRejectedAccessRequest write rejected subject access to redis.
func (auth *AuthorizationClient) LogRejectedAccessRequest(r *ladon.Request, p ladon.Policies, d ladon.Policies) {
	var conclusion string
	if len(d) > 1 {
		allowed := joinPoliciesNames(d[0 : len(d)-1])
		denied := d[len(d)-1].GetID()
		conclusion = fmt.Sprintf("policies %s allow access, but policy %s forcefully denied it", allowed, denied)
	} else if len(d) == 1 {
		denied := d[len(d)-1].GetID()
		conclusion = fmt.Sprintf("policy %s forcefully denied the access", denied)
	} else {
		conclusion = "no policy allowed access"
	}

	rstring, pstring, dstring := convertToString(r, p, d)
	record := analytics.AnalyticsRecord{
		TimeStamp:  time.Now().Unix(),
		Username:   r.Context[constant.AuthIdentityKey].(string),
		Effect:     ladon.DenyAccess,
		Conclusion: conclusion,
		Request:    rstring,
		Policies:   pstring,
		Deciders:   dstring,
	}

	record.SetExpiry(0)
	baseLog.Debugf("subject access review rejected: %+v", record)
	_ = analytics.GetAnalytics().RecordHit(&record)
}

// LogGrantedAccessRequest write granted subject access to redis.
func (auth *AuthorizationClient) LogGrantedAccessRequest(r *ladon.Request, p ladon.Policies, d ladon.Policies) {
	conclusion := fmt.Sprintf("policies %s allow access", joinPoliciesNames(d))
	rstring, pstring, dstring := convertToString(r, p, d)
	record := analytics.AnalyticsRecord{
		TimeStamp:  time.Now().Unix(),
		Username:   r.Context[constant.AuthIdentityKey].(string),
		Effect:     ladon.AllowAccess,
		Conclusion: conclusion,
		Request:    rstring,
		Policies:   pstring,
		Deciders:   dstring,
	}
	record.SetExpiry(0)

	baseLog.Debugf("subject access review granted: %+v", record)
	_ = analytics.GetAnalytics().RecordHit(&record)
}

func joinPoliciesNames(policies ladon.Policies) string {
	names := []string{}
	for _, policy := range policies {
		names = append(names, policy.GetID())
	}

	return strings.Join(names, ", ")
}

func convertToString(r *ladon.Request, p ladon.Policies, d ladon.Policies) (string, string, string) {
	rbytes, _ := json.Marshal(r)
	pbytes, _ := json.Marshal(p)
	dbytes, _ := json.Marshal(d)

	return string(rbytes), string(pbytes), string(dbytes)
}
